HTMX aims to provide access to modern browser functionality directly in HTML code, without a single line of JavaScript.
What Is HTMX?
HTMX is a small, dependency-free, extendable library that allows you to access modern browser features directly from HTML, instead of using JavaScript.
Specifically, it gives you access to AJAX (i.e., fetching content without reloading the whole page), CSS Transitions, WebSockets, and Server Sent Events directly via HTML attributes.
An example! Take a look at this snippet:
<button hx-get="/api/v1/hello-world" hx-swap="outerHTML">Click Me</button>
The special hx-get and hx-swap attributes tell HTMX:
“When a user clicks on this button, instruct the browser to perform an AJAX request to the ‘/api/v1/hello-world’ endpoint, and replace the entire button with the HTML content returned by the server”
In JavaScript, achieving the same result would take dozens of lines of code.
That is the power of HTMX!
HTMX Overview: Syntax, Features, and Capabilities
The core idea behind HTMX is the ability to send AJAX requests directly from HTML, with no JavaScript involved.
This is possible thanks to the following attributes:
hx-get: To perform a GET request to the given URL.
hx-post: To perform a POST request to the given URL.
hx-put: To perform a PUT request to the given URL.
hx-patch: To perform a PATCH request to the given URL.
hx-delete: To perform a DELETE request to the given URL.
When a specific event is triggered, the HTML element involving one of these HTMX attributes will make an AJAX request of the specified type to the given URL.
Consider the example below:
<button hx-post="/api/v1/products/buy">Buy</button>
This tells the browser:
“When a user clicks on the <button>, make a POST request to the URL ‘/api/v1/products/buy’ and load the response into the inner HTML of the <button>”
Request Triggers
By default, AJAX requests made by HTMX are triggered by the “natural” event associated with the HTML element:
change: For <input>, <textarea> and <select> elements.
submit: For the <form> element.
click: For every other element.
Going back to the snippet seen earlier, it should now be clear why the action that triggers the request is a click, even if not specified.
To modify the default trigger behavior, you can use the hx-trigger attribute to set which HTML event will cause the request.
Check out the list of events supported by HTML.
Take a look at the example below:
<span hx-get="/api/v1/products" hx-trigger="mouseenter">Hover Me!</span>
This tells the browser:
"When a user moves the mouse over the <span>, perform a GET request to the URL ‘/api/v1/products’ and render the response in the inner HTML of the <span>”
Keep in mind that hx-trigger also supports modifiers and filters to tailor the triggering logic to your needs.
Plus, HTMX provides the following special events:
load: Fires when the element is loaded for the first time.
revealed: Fires once when the element is scrolled into the viewport.
intersect: Fires once when the element intersects with the viewport.
As opposed to revealed, it accepts an optional CSS selector of the root element for intersection and a float number between 0.0 and 1.0 to indicate the amount of intersection to trigger the event on.
Query Parameters and Body Data
The way HTMX handles parameters and body data changes depending on the type of the request:
GET requests: The query parameters should be specified in the URL passed to hx-get.
By default, hx-get does not automatically include any parameters to the request.
Anyway, you can control that with the hx-params attribute as explained in the documentation.
Non-GET requests: If an element is a <form>, the body will include the values of all inputs within it, using their name attribute as the parameter name.
If it is not a <form>, the body will include the values of all the inputs of the nearest enclosing <form>.
Otherwise, if it has a value attribute, it will be used in the body.
When the default behavior is not enough, the hx-include and hx-params attributes allows you to control which values and which parameters to set, respectively.
Otherwise, you can programmatically modify the body fields by listening to the htmx:configRequest event.
Result Content Handling
By default, HTMX replaces the inner HTML of the element firing the request with the HTML returned by the AJAX call.
This means that HTMX-compliant AJAX endpoints should return HTML code.
To change the swap strategy, use the hx-swap attribute.
That supports the following values:
innerHTML: Replace the inner HTML of the target element.
outerHTML: Replace the entire target element with the response.
beforebegin: Insert the response before the target element.
afterbegin: Insert the response before the first child of the target element.
beforeend: Insert the response after the last child of the target element.
afterend: Insert the response after the target element.
delete: Delete the target element regardless of the response.
none: Does not append the content from the response.
You can change the target element the swap logic refers to with the hx-target attribute, which accepts a CSS selector.
Note that the attribute supports multiple triggers, each one separated by comma.
Focus now on the following snippet:
<button
hx-post="/api/v1/comments"
hx-trigger="click"
hx-swap=".comments"
hx-target="afterend"
>
Comment
</button>
This tells the browser:
“When a user clicks the <button>, perform a POST request to the URL ‘/api/v1/comments’ and add the resulting HTML to the .comments element”
HTMX in Action: Integration With ApostropheCMS
You now know what HTMX is, why it was created, and what it brings to the table.
All that remains is to see it in action in a real-world example.
What better way to do that than by integrating it with Apostrophe? If you are not familiar with this technology, ApostropheCMS is an open-source CMS and website builder built on top of modern technologies such as MongoDB and Node.js.
The starting point will be the blog application built in the “How to Build a Blog with the Apostrophe Blog Module” tutorial.
You will learn how to add HTMX and use it to achieve the following dynamic interactions:
“Load More” functionality
Infinite scroll loading
Live content filtering
Let’s dive in!
Getting Started
First, make sure to meet ApostropheCMS's system requirements.
Next, launch the command below to clone the GitHub repository of the blog application you will soon extend with HTMX:
git clone https://github.com/Tonel/apostrophe-blog
Install the project’s dependencies:
npm install
Then, fire the following command to build the ApostropheCMS UI and start up the blog:
npm run dev
Open https://localhost:3000 in the browser and you should see:
Follow the instructions, log in, and get familiar with the application.
Play with the UI and populate the blog with several posts.
You can then find the blog's home page at http://localhost:3000/blog.
Great! If you want to learn more about how this application works and was built, take a look at our tutorial.
Integrating HTMX
As stated in the documentation, integrating HTMX into an application boils down to adding a <script> tag to the document <head>.
No build tools or special configurations are required.
The fastest way to get going is to load the library via a CDN:
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
The goal of this section is to use HTMX to add dynamic interactions to the blog home page.
So, you need to add the <script> instruction to the HTML document of that page.
To add HTMX to the blog home page, follow the /modules/@apostrophecms/blog-page/views/ path and open the index.html file.
Paste the following line after the title block:
{% block extraHead %}
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
{% endblock %}
extraHead is a block from the ApostropheCMS core layout template that allows you to add HTML elements at the end of the <head> tag.
If you instead want to have HTMX in all pages, you can add it to your project's dependencies with:
npm install htmx.org
Then, import it in the modules/asset/ui/src/index.js file:
import 'htmx.org';
export default () => {
// your own project-level JS...
};
Open http://localhost:3000/blog in the browser and inspect its source code.
You should see the following HTML:
<!DOCTYPE html>
<html lang="en" >
<head>
<link href="/apos-frontend/default/apos-bundle.css" rel="stylesheet" />
<title>My Fantastic Blog </title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
</head>
<!-- Omitted for brevity...
-->
Well done! The HTMX dependency script was added as required.
Adding a Load More Button With HTMX
Right now, when the blog has more than 10 posts, the home page shows a pagination element.
Click on one of these buttons, and you will be redirected to the selected page.
For example, “2” brings you to /blog?page=2.
What if you wanted to replace that interaction with a “Load More” button? Thanks to HTMX, that will take only a few lines of code!
When the “Load More” button is clicked, the page should perform an AJAX request to retrieve the HTML to render other blog post cards.
You could think of using HTMX to make the button target the /blog?page=2 endpoint and swap the current content with the retrieved HTML.
However, keep in mind that /blog?page=2 returns the entire HTML of a new page.
Following this approach is not recommended, as you ideally want to replace only a small portion of the page not the all the page.
Specifically, you want to swap the “Load More” button with the new blog post tabs.
To get close to the goal, you can take advantage of the aposRefresh=1 parameter.
This query parameter instructs ApostropheCMS to return the rendered HTML of the inner template, excluding the wrapping markup.
For example, the /blog?page=2&aposRefresh=1 endpoint returns something like:
<div class="bg-container">
<h1 class="bg-h1">My Fantastic Blog</h1>
<h3>Filters</h3>
<ul class="bg-filter-list">
<li>
<a href="/blog?year=2023">2023</a>
</li>
<li>
<a href="/blog?year=2022">2022</a>
</li>
<li>
<a href="/blog?year=2021">2021</a>
</li>
<li>
<a class="is-active" href="/blog">All</a>
</li>
</ul>
<h3>Blog post</h3>
<div class="bg-preview-card">
<div class="bg-preview-date">
Released on September 4, 2022
</div>
<div class="bg-preview-title">
<a href="/blog/lorem-ipsum-8">Lorem Ipsum 8</a>
</div>
</div>
<div class="bg-preview-card">
<div class="bg-preview-date">
Released on August 2, 2022
</div>
<div class="bg-preview-title">
<a href="/blog/lorem-ipsum-12">Lorem Ipsum 12</a>
</div>
</div>
<!-- Omitted for brevity...
-->
</div>
Much better! As you can see, this HTML involves only a partial section of the page.
At the same time, it still includes the title and filter elements.
To ignore them, you can change the index.html file so that it behaves differently based on the presence of a custom query parameter.
Achieve that by updating the rendering logic inside the main block of /modules/@apostrophecms/blog-page/views/index.html as follows:
{% block main %}
{% if data.query.showOnlyList != "1" %}
<div class="bg-container">
<h1 class="bg-h1">{{ data.page.title }}</h1>
<h3>{{ __t('aposBlog:filters') }}</h3>
{% render filters.render({
filters: data.piecesFilters,
query: data.query,
url: data.page._url
}) %}
<h3>{{ __t('aposBlog:pluralLabel') }}</h3>
{{ renderBlogList() }}
</div>
{% else %}
{{ renderBlogList() }}
{% endif %}
{% endblock %}
Now, when the showOnlyList=1 query parameter is present, the endpoint for the blog home page will return only the list of blog posts.
Otherwise, it will return the entire page as before.
You may be wondering what renderBlogList() is.
This is a custom Nunjucks macro that renders the list of blog post cards and the “Load More” button:
{% set page = data.query.page | default(1) | int %}
{% for piece in data.pieces %}
<div class="bg-preview-card">
<div class="bg-preview-date">
{{ __t('aposBlog:releasedOn') }} {{ piece.publishedAt | date('MMMM D, YYYY') }}
</div>
<div class="bg-preview-title">
<a href="{{ piece._url }}">{{ piece.title }}</a>
</div>
</div>
{% endfor %}
<div class="load-more-div">
{% if page != data.totalPages %}
<button
hx-get="/blog?page={{page + 1}}&year={{data.query.year}}&showOnlyList=1&aposRefresh=1"
hx-target=".load-more-div"
hx-swap="outerHTML"
>
Load More
</button>
{% endif %}
</div>
{% endmacro %}
Focus on the .load-more-div HTML element.
That is where the HTMX magic happens!
page is a variable that stores the current page of the blog posts to render.
If there are still some blogs to load, the “Load More” button is added to the page.
When the user clicks it, the webpage makes an AJAX request to the endpoint specified in hx-get.
This will return the rendered HTML with the list of the posts related to the next page, considering the optional year filter.
HTMX will then replace the outer HTML of the .load-more-div element with that content.
Put it all together, and you will get:
<!-- /modules/@apostrophecms/blog-page/views/index.html -->
{% extends data.outerLayout %}
{% import "filters.html" as filters %}
{% import "@apostrophecms/pager:macros.html" as pager with context %}
{% block title %}{{ data.page.title }} {% endblock %}
{% block extraHead %}
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
{% endblock %}
{% macro renderBlogList() %}
{% set page = data.query.page | default(1) | int %}
{% for piece in data.pieces %}
<div class="bg-preview-card">
<div class="bg-preview-date">
{{ __t('aposBlog:releasedOn') }} {{ piece.publishedAt | date('MMMM D, YYYY') }}
</div>
<div class="bg-preview-title">
<a href="{{ piece._url }}">{{ piece.title }}</a>
</div>
</div>
{% endfor %}
<div class="load-more-div">
{% if page != data.totalPages %}
<button
hx-get="/blog?page={{page + 1}}&year={{data.query.year}}&showOnlyList=1&aposRefresh=1"
hx-target=".load-more-div"
hx-swap="outerHTML"
>
Load More
</button>
{% endif %}
</div>
{% endmacro %}
{% block main %}
{% if data.query.showOnlyList != "1" %}
<div class="bg-container">
<h1 class="bg-h1">{{ data.page.title }}</h1>
<h3>{{ __t('aposBlog:filters') }}</h3>
{% render filters.render({
filters: data.piecesFilters,
query: data.query,
url: data.page._url
}) %}
<h3>{{ __t('aposBlog:pluralLabel') }}</h3>
{{ renderBlogList() }}
</div>
{% else %}
{{ renderBlogList() }}
{% endif %}
{% endblock %}
Note that the pagination element has been removed by the template.
You no longer need it.
Style the “Load More” button in /modules/asset/ui/src/scss/_blog.scss, and you are ready to test it.
This is how your new http://localhost:3000/blog page behaves:
If you inspect the “Network” section of the browser's DevTools, you will notice that the “Load More” button triggers the following AJAX call:
This will return the HTML containing the new blog post cards to add to the page.
Congrats! You just used HTMX to add a click-to-load feature to your blog.
Note: You can find the entire code of this example in the htmx-load-more branch of the GitHub repository supporting the article.
Using HTMX to Implement Infinite Scrolling
Now that you have seen how to implement a “Load More” button with HTMX, achieving infinite scrolling is easy.
All you have to do is change the renderBlogList() function as follows:
{% macro renderBlogList() %}
{% set page = data.query.page | default(1) | int %}
{% for piece in data.pieces %}
<div class="bg-preview-card"
{% if loop.last %}
hx-get="/blog?page={{page + 1}}&year={{data.query.year}}&showOnlyList=1&aposRefresh=1"
hx-trigger="revealed"
hx-swap="afterend"
{% endif %}
>
<div class="bg-preview-date">
{{ __t('aposBlog:releasedOn') }} {{ piece.publishedAt | date('MMMM D, YYYY') }}
</div>
<div class="bg-preview-title">
<a href="{{ piece._url }}">{{ piece.title }}</a>
</div>
</div>
{% endfor %}
{% endmacro %}
The revealed HTMX event triggers when an element is scrolled into the viewport.
By adding it to the last blog post card, you can implement infinite scroll loading behavior:
Awesome! Focus on the scrollbar to notice that the page adds dynamic content as the user scrolls down.
Note: You can find the complete code of this example in the htmx-infinite-scrolling branch.
Achieving Live Content Filtering Through HTMX
The goal here is to use HTMX to dynamically update the content of the page when clicking on a year filter button.
In this case, you do not have to update the blog post list, but replace it entirely.
Also, you need to override the HTML section that contains the filters to ensure that the correct button is enabled.
First, update filters.html to introduce the HTMX logic:
<!-- /modules/@apostrophecms/blog-page/views/filters.html -->
{%- macro here(url, changes) -%}
{{ url | build({
year: data.query.year
}, {
excludeContainer: 1,
aposRefresh: 1
}, changes) }}
{%- endmacro -%}
{% fragment render(data) %}
<ul class="bg-filter-list">
{% for year in data.filters.year %}
<li>
<button
class="{{ 'is-active' if data.query.year == year.value }}"
hx-get="{{ here(data.url, { year: year.value })}}"
hx-target=".blog-page"
hx-swap="outerHTML"
>
{{ __t(year.label) }}
</button>
</li>
{% endfor %}
</ul>
{% endfragment %}
Note that the year filter elements are no longer links, but buttons that target a specific endpoint via HTMX.
In particular, here() has been updated to produce the URL required to dynamically retrieve the desired HTML content.
Keep in mind that build() accepts as many query parameter objects as you need.
The excludeContainer query parameter will control the rendering logic in the index.html file as below:
<!-- /modules/@apostrophecms/blog-page/views/filters.html -->
{% extends data.outerLayout %}
{% import "filters.html" as filters %}
{% import "@apostrophecms/pager:macros.html" as pager with context %}
{% block title %}{{ data.page.title }} {% endblock %}
{% block extraHead %}
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
{% endblock %}
{% block main %}
{% if data.query.excludeContainer != "1" %}
<div class="bg-container">
<h1 class="bg-h1">{{ data.page.title }}</h1>
{% endif %}
<div class="blog-page">
<h3>{{ __t('aposBlog:filters') }}</h3>
{% render filters.render({
filters: data.piecesFilters,
query: data.query,
url: data.page._url
}) %}
<h3>{{ __t('aposBlog:pluralLabel') }}</h3>
{% for piece in data.pieces %}
<div class="bg-preview-card">
<div class="bg-preview-date">
{{ __t('aposBlog:releasedOn') }} {{ piece.publishedAt | date('MMMM D, YYYY') }}
</div>
<div class="bg-preview-title">
<a href="{{ piece._url }}">{{ piece.title }}</a>
</div>
</div>
{% endfor %}
<div class="pagination">
{{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url | build({ excludeContainer: null })) }}
</div>
</div>
{% if data.query.excludeContainer != "1" %}
</div>
{% endif %}
{% endblock %}
The home page of the blog will now have real-time filtering capabilities:
Awesome! You have just learned how HTMX simplifies the integration of dynamic interactions into an existing frontend application.
The next step is to add a loader through the hx-indicator attribute.
Check out the docs to see all the other cool features HTMX has to offer!
Note: You can find the entire code of the example in the htmx-content-filtering branch.
The most common attributes when using htmx.
hx-get
issues a GET to the specified URL
hx-post
issues a POST to the specified URL
hx-on*
handle events with inline scripts on elements
hx-push-url
push a URL into the browser location bar to create history
hx-select
select content to swap in from a response
hx-select-oob
select content to swap in from a response, somewhere other than the target (out of band)
hx-swap
controls how content will swap in (outerHTML, beforeend, afterend, …)
hx-swap-oob
mark element to swap in from a response (out of band)
hx-target
specifies the target element to be swapped
hx-trigger
specifies the event that triggers the request
hx-vals
add values to submit with the request (JSON format)
Additional Attribute Reference
All other attributes available in htmx.
hx-boost
add progressive enhancement for links and forms
hx-confirm
shows a confirm() dialog before issuing a request
hx-delete
issues a DELETE to the specified URL
hx-disable
disables htmx processing for the given node and any children nodes
hx-disabled-elt
adds the disabled attribute to the specified elements while a request is in flight
hx-disinherit
control and disable automatic attribute inheritance for child nodes
hx-encoding
changes the request encoding type
hx-ext
extensions to use for this element
hx-headers
adds to the headers that will be submitted with the request
hx-history
prevent sensitive data being saved to the history cache
hx-history-elt
the element to snapshot and restore during history navigation
hx-include
include additional data in requests
hx-indicator
the element to put the htmx-request class on during the request
hx-inherit
control and enable automatic attribute inheritance for child nodes if it has been disabled by default
hx-params
filters the parameters that will be submitted with a request
hx-patch
issues a PATCH to the specified URL
hx-preserve
specifies elements to keep unchanged between requests
hx-prompt
shows a prompt() before submitting a request
hx-put
issues a PUT to the specified URL
hx-replace-url
replace the URL in the browser location bar
hx-request
configures various aspects of the request
hx-sync
control how requests made by different elements are synchronized
hx-validate
force elements to validate themselves before a request
hx-vars
adds values dynamically to the parameters to submit with the request (deprecated, please use hx-vals)
CSS Class Reference
htmx-added
Applied to a new piece of content before it is swapped, removed after it is settled.
htmx-indicator
A dynamically generated class that will toggle visible (opacity:1) when a htmx-request class is present
htmx-request
Applied to either the element or the element specified with hx-indicator while a request is ongoing
htmx-settling
Applied to a target after content is swapped, removed after it is settled. The duration can be modified via hx-swap.
htmx-swapping
Applied to a target before any content is swapped, removed after it is swapped. The duration can be modified via hx-swap.
HTTP Header Reference
Request Headers Reference
HX-Boosted
indicates that the request is via an element using hx-boostHX-Current-URL
the current URL of the browser
HX-History-Restore-Request
“true” if the request is for history restoration after a miss in the local history cache
HX-Prompt
the user response to an hx-promptHX-Request
always “true”
HX-Target
the id of the target element if it exists
HX-Trigger-Name
the name of the triggered element if it exists
HX-Trigger
the id of the triggered element if it exists
Response Headers Reference
HX-Location
allows you to do a client-side redirect that does not do a full page reload
HX-Push-Url
pushes a new url into the history stack
HX-Redirect
can be used to do a client-side redirect to a new location
HX-Refresh
if set to “true” the client-side will do a full refresh of the page
HX-Replace-Url
replaces the current URL in the location bar
HX-Reswap
allows you to specify how the response will be swapped. See hx-swap for possible values
HX-Retarget
a CSS selector that updates the target of the content update to a different element on the page
HX-Reselect
a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing hx-select on the triggering element
HX-Trigger
allows you to trigger client-side events
HX-Trigger-After-Settle
allows you to trigger client-side events after the settle step
HX-Trigger-After-Swap
allows you to trigger client-side events after the swap step
Event Reference
htmx:abort
send this event to an element to abort a request
htmx:afterOnLoad
triggered after an AJAX request has completed processing a successful response
htmx:afterProcessNode
triggered after htmx has initialized a node
htmx:afterRequest
triggered after an AJAX request has completed
htmx:afterSettle
triggered after the DOM has settled
htmx:afterSwap
triggered after new content has been swapped in
htmx:beforeCleanupElement
triggered before htmx disables an element or removes it from the DOM
htmx:beforeOnLoad
triggered before any response processing occurs
htmx:beforeProcessNode
triggered before htmx initializes a node
htmx:beforeRequest
triggered before an AJAX request is made
htmx:beforeSwap
triggered before a swap is done, allows you to configure the swap
htmx:beforeSend
triggered just before an ajax request is sent
htmx:configRequest
triggered before the request, allows you to customize parameters, headers
htmx:confirm
triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request
htmx:historyCacheError
triggered on an error during cache writing
htmx:historyCacheMiss
triggered on a cache miss in the history subsystem
htmx:historyCacheMissError
triggered on a unsuccessful remote retrieval
htmx:historyCacheMissLoad
triggered on a successful remote retrieval
htmx:historyRestore
triggered when htmx handles a history restoration action
htmx:beforeHistorySave
triggered before content is saved to the history cache
htmx:load
triggered when new content is added to the DOM
htmx:noSSESourceError
triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined
htmx:onLoadError
triggered when an exception occurs during the onLoad handling in htmx
htmx:oobAfterSwap
triggered after an out of band element as been swapped in
htmx:oobBeforeSwap
triggered before an out of band element swap is done, allows you to configure the swap
htmx:oobErrorNoTarget
triggered when an out of band element does not have a matching ID in the current DOM
htmx:prompt
triggered after a prompt is shown
htmx:pushedIntoHistory
triggered after an url is pushed into history
htmx:responseError
triggered when an HTTP response error (non-200 or 300 response code) occurs
htmx:sendError
triggered when a network error prevents an HTTP request from happening
htmx:sseError
triggered when an error occurs with a SSE source
htmx:sseOpen
triggered when a SSE source is opened
htmx:swapError
triggered when an error occurs during the swap phase
htmx:targetError
triggered when an invalid target is specified
htmx:timeout
triggered when a request timeout occurs
htmx:validation:validate
triggered before an element is validated
htmx:validation:failed
triggered when an element fails validation
htmx:validation:halted
triggered when a request is halted due to validation errors
htmx:xhr:abort
triggered when an ajax request aborts
htmx:xhr:loadend
triggered when an ajax request ends
htmx:xhr:loadstart
triggered when an ajax request starts
htmx:xhr:progress
triggered periodically during an ajax request that supports progress events
Htmx has some configuration options that can be accessed either programmatically or declaratively. They are
listed below:
htmx.config.historyEnabled
defaults to true, really only useful for testing
htmx.config.historyCacheSize
defaults to 10
htmx.config.refreshOnHistoryMiss
defaults to false, if set to true htmx will issue a full page refresh on history misses rather than use an AJAX request
htmx.config.defaultSwapStyle
defaults to innerHTMLhtmx.config.defaultSwapDelay
defaults to 0
htmx.config.defaultSettleDelay
defaults to 20
htmx.config.includeIndicatorStyles
defaults to true (determines if the indicator styles are loaded)
htmx.config.indicatorClass
defaults to htmx-indicatorhtmx.config.requestClass
defaults to htmx-requesthtmx.config.addedClass
defaults to htmx-addedhtmx.config.settlingClass
defaults to htmx-settlinghtmx.config.swappingClass
defaults to htmx-swappinghtmx.config.allowEval
defaults to true, can be used to disable htmx’s use of eval for certain features (e.g. trigger filters)
htmx.config.allowScriptTags
defaults to true, determines if htmx will process script tags found in new content
htmx.config.inlineScriptNonce
defaults to '', meaning that no nonce will be added to inline scripts
htmx.config.inlineSlyeNonce
defaults to '', meaning that no nonce will be added to inline styles
htmx.config.attributesToSettle
defaults to ["class", "style", "width", "height"], the attributes to settle during the settling phase
htmx.config.wsReconnectDelay
defaults to full-jitterhtmx.config.wsBinaryType
defaults to blob, the the type of binary data being received over the WebSocket connection
htmx.config.disableSelector
defaults to [hx-disable], [data-hx-disable], htmx will not process elements with this attribute on it or a parent
htmx.config.withCredentials
defaults to false, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates
htmx.config.timeout
defaults to 0, the number of milliseconds a request can take before automatically being terminated
htmx.config.scrollBehavior
defaults to ‘instant’, the behavior for a boosted link on page transitions. The allowed values are auto, instant and smooth. Instant will scroll instantly in a single jump, smooth will scroll smoothly, while auto will behave like a vanilla link.
htmx.config.defaultFocusScroll
if the focused element should be scrolled into view, defaults to false and can be overridden using the focus-scroll swap modifier.
htmx.config.getCacheBusterParam
defaults to false, if set to true htmx will append the target element to the GET request in the format org.htmx.cache-buster=targetElementIdhtmx.config.globalViewTransitions
if set to true, htmx will use the View Transition API when swapping in new content.
htmx.config.methodsThatUseUrlParams
defaults to ["get"], htmx will format requests with these methods by encoding their parameters in the URL, not the request body
htmx.config.selfRequestsOnly
defaults to true, whether to only allow AJAX requests to the same domain as the current document
htmx.config.ignoreTitle
defaults to false, if set to true htmx will not update the title of the document when a title tag is found in new content
htmx.config.scrollIntoViewOnBoost
defaults to true, whether or not the target of a boosted element is scrolled into the viewport. If hx-target is omitted on a boosted element, the target defaults to body, causing the page to scroll to the top.
htmx.config.triggerSpecsCache
defaults to null, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a proxy objecthtmx.config.allowNestedOobSwaps
defaults to true, whether to process OOB swaps on elements that are nested within the main response element. See Nested OOB Swaps.
You can set them directly in javascript, or you can use a meta tag:
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>
hx-boost
The hx-boost attribute allows you to “boost” normal anchors and form tags to use AJAX instead.
This has the nice fallback that, if the user does not have javascript enabled, the site will continue to work.
For anchor tags, clicking on the anchor will issue a GET request to the url specified in the href and will push the url so that a history entry is created.
The target is the <body> tag, and the innerHTML swap strategy is used by default.
All of these can be modified by using the appropriate attributes, except the click trigger.
For forms the request will be converted into a GET or POST, based on the method in the method attribute and will be triggered by a submit.
Again, the target will be the body of the page, and the innerHTML swap will be used.
The url will not be pushed, however, and no history entry will be created.
(You can use the
hx-push-url attribute if you want the url to be pushed.)
Here is an example of some boosted links:
<div hx-boost="true">
<a href="/page1">Go To Page 1</a>
<a href="/page2">Go To Page 2</a>
</div>
These links will issue an ajax GET request to the respective URLs and replace the body’s inner content with it.
Here is an example of a boosted form:
<form hx-boost="true" action="/example" method="post">
<input name="email" type="email" placeholder="Enter email...">
<button>Submit</button>
</form>
This form will issue an ajax POST to the given URL and replace the body’s inner content with it.
Notes
hx-boost is inherited and can be placed on a parent element Only links that are to the same domain and that are not local anchors will be boosted All requests are done via AJAX, so keep that in mind when doing things like redirects To find out if the request results from a boosted anchor or form, look for HX-Boosted in the request header Selectively disable boost on child elements with hx-boost="false"
Disable the replacement of elements via boost, and their children, with hx-preserve="true"
hx-confirm
The hx-confirm attribute allows you to confirm an action before issuing a request.
This can be useful in cases where the action is destructive and you want to ensure that the user really wants to do it.
Here is an example:
<button hx-delete="/account" hx-confirm="Are you sure you wish to delete your account?">
Delete My Account
</button>
Event details
The event triggered by hx-confirm contains additional properties in its detail:
triggeringEvent: the event that triggered the original request issueRequest(skipConfirmation=false): a callback which can be used to confirm the AJAX request question: the value of the hx-confirm attribute on the HTML element
Notes
hx-confirm is inherited and can be placed on a parent element
hx-confirm uses the browser’s window.confirm by default.
You can customize this behavior as shown in this example.
a boolean skipConfirmation can be passed to the issueRequest callback; if true (defaults to false), the window.confirm will not be called and the AJAX request is issued directly
hx-delete
The hx-delete attribute will cause an element to issue a DELETE to the specified URL and swap the HTML into the DOM using a swap strategy:
<button hx-delete="/account" hx-target="body">
Delete Your Account
</button>
This example will cause the button to issue a DELETE to /account and swap the returned HTML into the innerHTML of the body.
Notes
hx-delete is not inherited You can control the target of the swap using the hx-target attribute You can control the swap strategy by using the hx-swap attribute You can control what event triggers the request with the hx-trigger attribute You can control the data submitted with the request in various ways, documented here: Parameters
To remove the element following a successful DELETE, return a 200 status code with an empty body; if the server responds with a 204, no swap takes place, documented here: Requests & Responses
hx-disable
The hx-disable attribute will disable htmx processing for a given element and all its children.
This can be useful as a backup for HTML escaping, when you include user generated content in your site, and you want to prevent malicious scripting attacks.
The value of the tag is ignored, and it cannot be reversed by any content beneath it.
Notes
hx-disable is inherited
hx-disabled-elt
The hx-disabled-elt attribute allows you to specify elements that will have the disabled attribute added to them for the duration of the request.
The value of this attribute can be:
A CSS query selector of the element to disable.
this to disable the element itself
closest <CSS selector> which will find the closest ancestor element or itself, that matches the given CSS selector
(e.g.
closest fieldset will disable the closest to the element fieldset).
find <CSS selector> which will find the first child descendant element that matches the given CSS selector
next which resolves to element.nextElementSiblingnext <CSS selector> which will scan the DOM forward for the first element that matches the given CSS selector
(e.g.
next button will disable the closest following sibling button element)
previous which resolves to element.previousElementSiblingprevious <CSS selector> which will scan the DOM backwards for the first element that matches the given CSS selector.
(e.g previous input will disable the closest previous sibling input element)
Here is an example with a button that will disable itself during a request:
<button hx-post="/example" hx-disabled-elt="this">
Post It!
</button>
When a request is in flight, this will cause the button to be marked with the disabled attribute, which will prevent further clicks from occurring.
The hx-disabled-elt attribute also supports specifying multiple CSS selectors separated by commas to disable multiple elements during the request.
Here is an example that disables buttons and text input fields of a particular form during the request:
<form hx-post="/example" hx-disabled-elt="find input[type='text'], find button">
<input type="text" placeholder="Type here...">
<button type="submit">Send</button>
</form>
Notes
hx-disabled-elt is inherited and can be placed on a parent element
hx-disinherit
The default behavior for htmx is to “inherit” many attributes automatically: that is, an attribute such as
hx-target may be placed on a parent element, and all child elements will inherit that target.
The hx-disinherit attribute allows you to control this automatic attribute inheritance.
An example scenario is to allow you to place an hx-boost on the body element of a page, but overriding that behavior in a specific part of the page to allow for more specific behaviors.
htmx evaluates attribute inheritance as follows:
when hx-disinherit is set on a parent node
hx-disinherit="*" all attribute inheritance for this element will be disabled
hx-disinherit="hx-select hx-get hx-target" disable inheritance for only one or multiple specified attributes
<div hx-boost="true" hx-select="#content" hx-target="#content" hx-disinherit="*">
<a href="/page1">Go To Page 1</a> <!-- boosted with the attribute settings above -->
<a href="/page2" hx-boost="unset">Go To Page 1</a> <!-- not boosted -->
<button hx-get="/test" hx-target="this"></button> <!-- hx-select is not inherited -->
</div><div hx-boost="true" hx-select="#content" hx-target="#content" hx-disinherit="hx-target">
<!-- hx-select is automatically set to parent's value; hx-target is not inherited -->
<button hx-get="/test"></button>
</div><div hx-select="#content">
<div hx-boost="true" hx-target="#content" hx-disinherit="hx-select">
<!-- hx-target is automatically inherited from parent's value -->
<!-- hx-select is not inherited, because the direct parent does
disables inheritance, despite not specifying hx-select itself -->
<button hx-get="/test"></button>
</div>
</div>
The hx-encoding attribute allows you to switch the request encoding from the usual application/x-www-form-urlencoded encoding to multipart/form-data, usually to support file uploads in an ajax request.
The value of this attribute should be multipart/form-data.
The hx-encoding tag may be placed on parent elements.
Notes
hx-encoding is inherited and can be placed on a parent element
hx-ext
The hx-ext attribute enables an htmx extension for an element and all its children.
The value can be a single extension name or a comma separated list of extensions to apply.
The hx-ext tag may be placed on parent elements if you want a plugin to apply to an entire swath of the DOM,
and on the body tag for it to apply to all htmx requests.
Notes
hx-ext is both inherited and merged with parent elements, so you can specify extensions on any element in the DOM hierarchy and it will apply to all child elements.
You can ignore an extension that is defined by a parent node using hx-ext="ignore:extensionName"<div hx-ext="example">
"Example" extension is used in this part of the tree...
<div hx-ext="ignore:example">
...
but it will not be used in this part.
</div>
</div>
hx-get
The hx-get attribute will cause an element to issue a GET to the specified URL and swap the HTML into the DOM using a swap strategy:
<div hx-get="/example">Get Some HTML</div>
This example will cause the div to issue a GET to /example and swap the returned HTML into the innerHTML of the div.
Notes
hx-get is not inherited By default hx-get does not include any parameters.
You can use the hx-params attribute to change this You can control the target of the swap using the hx-target attribute You can control the swap strategy by using the hx-swap attribute You can control what event triggers the request with the hx-trigger attribute You can control the data submitted with the request in various ways, documented here: Parameters
An empty hx-get:"" will make a get request to the current url and will swap the current HTML page
hx-headers
The hx-headers attribute allows you to add to the headers that will be submitted with an AJAX request.
By default, the value of this attribute is a list of name-expression values in JSON (JavaScript Object Notation) format.
If you wish for hx-headers to evaluate the values given, you can prefix the values with javascript: or js:.
<div hx-get="/example" hx-headers='{"myHeader": "My Value"}'>Get Some HTML, Including A Custom Header in the Request</div>
Security Considerations
By default, the value of hx-headers must be valid JSON.
It is not dynamically computed.
If you use the javascript: prefix, be aware that you are introducing security considerations, especially when dealing with user input such as query strings or user-generated content, which could introduce a Cross-Site Scripting (XSS) vulnerability.
Notes
hx-headers is inherited and can be placed on a parent element.
A child declaration of a header overrides a parent declaration.
hx-history-elt
The hx-history-elt attribute allows you to specify the element that will be used to snapshot and restore page state during navigation.
By default, the body tag is used.
This is typically good enough for most setups, but you may want to narrow it down to a child element.
Just make sure that the element is always visible in your application, or htmx will not be able to restore history navigation properly.
Here is an example:
<html>
<body>
<div id="content" hx-history-elt>
...
</div>
</body>
</html>
Notes
hx-history-elt is not inherited In most cases we don’t recommend narrowing the history snapshot
hx-history
Set the hx-history attribute to false on any element in the current document, or any html fragment loaded into the current document by htmx, to prevent sensitive data being saved to the localStorage cache when htmx takes a snapshot of the page state.
History navigation will work as expected, but on restoration the URL will be requested from the server instead of the history cache.
Here is an example:
<html>
<body>
<div hx-history="false">
...
</div>
</body>
</html>
Notes
hx-history="false" can be present anywhere in the document to embargo the current page state from the history cache (i.e.
even outside the element specified for the history snapshot hx-history-elt).
hx-include
The hx-include attribute allows you to include additional element values in an AJAX request.
The value of this attribute can be:
A CSS query selector of the elements to include.
this which will include the descendants of the element.
closest <CSS selector> which will find the closest ancestor element or itself, that matches the given CSS selector
(e.g.
closest tr will target the closest table row to the element).
find <CSS selector> which will find the first child descendant element that matches the given CSS selector.
next <CSS selector> which will scan the DOM forward for the first element that matches the given CSS selector.
(e.g.
next .error will target the closest following sibling element with error class)
previous <CSS selector> which will scan the DOM backwards for the first element that matches the given CSS selector.
(e.g previous .error will target the closest previous sibling with error class)
Here is an example that includes a separate input value:
<div>
<button hx-post="/register" hx-include="[name='email']">
Register!
</button>
Enter email: <input name="email" type="email"/>
</div>
This is a little contrived as you would typically enclose both of these elements in a form and submit the value automatically, but it demonstrates the concept.
Note that if you include a non-input element, all input elements enclosed in that element will be included.
Notes
hx-include is inherited and can be placed on a parent element While hx-include is inherited, it is evaluated from the element triggering the request.
It is easy to get confused when working with the extended selectors such as find and closest.<div hx-include="find input">
<button hx-post="/register">
Register!
</button>
Enter email: <input name="email" type="email"/>
</div>
In the above example, when clicking on the button, the find input selector is resolved from the button itself, which does not return any element here, since the button doesn’t have any input child, thus in this case, raises an error.
A standard CSS selector resolves to document.querySelectorAll and will include multiple elements, while the extended selectors such as find or next only return a single element at most to include
hx-indicator
The hx-indicator attribute allows you to specify the element that will have the htmx-request class added to it for the duration of the request.
This can be used to show spinners or progress indicators while the request is in flight.
The value of this attribute is a CSS query selector of the element or elements to apply the class to,
or the keyword closest, followed by a CSS selector, which will find the closest ancestor element or itself, that matches the given CSS selector (e.g.
closest tr);
Here is an example with a spinner adjacent to the button:
<div>
<button hx-post="/example" hx-indicator="#spinner">
Post It!
</button>
<img id="spinner" class="htmx-indicator" src="/img/bars.svg"/>
</div>
When a request is in flight, this will cause the htmx-request class to be added to the #spinner image.
The image also has the htmx-indicator class on it, which defines an opacity transition that will show the spinner:
.htmx-indicator{
opacity:0;
transition: opacity 500ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
.htmx-request.htmx-indicator{
opacity:1
}
If you would prefer a different effect for showing the spinner you could define and use your own indicator CSS.
Here is an example that uses display rather than opacity (Note that we use my-indicator instead of htmx-indicator):
.my-indicator{
display:none;
}
.htmx-request .my-indicator{
display:inline;
}
.htmx-request.my-indicator{
display:inline;
}
Note that the target of the hx-indicator selector need not be the exact element that you want to show: it can be any element in the parent hierarchy of the indicator.
Finally, note that the htmx-request class by default is added to the element causing the request, so you can place an indicator inside of that element and not need to explicitly call it out with the hx-indicator attribute:
<button hx-post="/example">
Post It!
<img class="htmx-indicator" src="/img/bars.svg"/>
</button>
Demo
This simulates what a spinner might look like in that situation:
Notes
hx-indicator is inherited and can be placed on a parent element In the absence of an explicit indicator, the htmx-request class will be added to the element triggering the request If you want to use your own CSS but still use htmx-indicator as class name, then you need to disable includeIndicatorStyles.
See Configuring htmx.
The easiest way is to add this to the <head> of your HTML:
<meta name="htmx-config" content='{"includeIndicatorStyles": false}'>
hx-inherit
The default behavior for htmx is to “inherit” many attributes automatically: that is, an attribute such as
hx-target may be placed on a parent element, and all child elements will inherit that target.
Some people do not like this feature and instead prefer to explicitly specify inheritance for attributes.
To support this mode of development, htmx offers the htmx.config.disableInheritance setting, which can be set to
false to prevent inheritance from being the default behavior for any of the htmx attributes.
The hx-inherit attribute allows you to control the inheritance of attributes manually.
htmx evaluates attribute inheritance as follows:
when hx-inherit is set on a parent node
inherit="*" all attribute inheritance for this element will be enabled
hx-inherit="hx-select hx-get hx-target" enable inheritance for only one or multiple specified attributes
Here is an example of a div that shares an hx-target attribute for a set of anchor tags when htmx.config.disableInheritance is set to false:
<div hx-target="#tab-container" hx-inherit="hx-target">
<a hx-boost="true" href="/tab1">Tab 1</a>
<a hx-boost="true" href="/tab2">Tab 2</a>
<a hx-boost="true" href="/tab3">Tab 3</a>
</div>
The hx-on* attributes allow you to embed scripts inline to respond to events directly on an element; similar to the
onevent properties found in HTML, such as onClick.
The hx-on* attributes improve upon onevent by enabling the handling of any arbitrary JavaScript event,
for enhanced Locality of Behaviour (LoB) even when dealing with non-standard DOM events.
For example, these attributes allow you to handle htmx events.
With hx-on attributes, you specify the event name as part of the attribute name, after a colon.
So, for example, if you want to respond to a click event, you would use the attribute hx-on:click:
<div hx-on:click="alert('Clicked!')">Click</div>
Note that this syntax can be used to capture all htmx events, as well as most other custom events, in addition to the standard DOM events.
One gotcha to note is that DOM attributes do not preserve case.
This means, unfortunately, an attribute like
hx-on:htmx:beforeRequestwill not work, because the DOM lowercases the attribute names.
Fortunately, htmx supports both camel case event names and also kebab-case event names, so you can use hx-on:htmx:before-request instead.
In order to make writing htmx-based event handlers a little easier, you can use the shorthand double-colon hx-on:: for htmx events, and omit the “htmx” part:
<!-- These two are equivalent -->
<button hx-get="/info" hx-on:htmx:before-request="alert('Making a request!')">
Get Info!
</button>
<button hx-get="/info" hx-on::before-request="alert('Making a request!')">
Get Info!
</button>
If you wish to handle multiple different events, you can simply add multiple attributes to an element:
<button hx-get="/info"
hx-on::before-request="alert('Making a request!')"
hx-on::after-request="alert('Done making a request!')">
Get Info!
</button>
Finally, in order to make this feature compatible with some templating languages (e.g.
JSX) that do not like having a colon (:)
in HTML attributes, you may use dashes in the place of colons for both the long form and the shorthand form:
<!-- These two are equivalent -->
<button hx-get="/info" hx-on-htmx-before-request="alert('Making a request!')">
Get Info!
</button>
<button hx-get="/info" hx-on--before-request="alert('Making a request!')">
Get Info!
</button>
hx-on (deprecated)
The value is an event name, followed by a colon :, followed by the script:
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')">
Get Info!
</button>
Multiple handlers can be defined by putting them on new lines:
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')
htmx:afterRequest: alert('Done making a request!')">
Get Info!
</button>
Symbols
Like onevent, two symbols are made available to event handler scripts:
this - The element on which the hx-on attribute is defined
event - The event that triggered the handler
Notes
hx-on is not inherited, however due to
event bubbling,
hx-on attributes on parent elements will typically be triggered by events on child elements
hx-on:* and hx-on cannot be used together on the same element; if hx-on:* is present, the value of an hx-on attribute on the same element will be ignored.
The two forms can be mixed in the same document, however.
hx-params
The hx-params attribute allows you to filter the parameters that will be submitted with an AJAX request.
The possible values of this attribute are:
* - Include all parameters (default)
none - Include no parameters
not <param-list> - Include all except the comma separated list of parameter names
<param-list> - Include all the comma separated list of parameter names
<div hx-get="/example" hx-params="*">Get Some HTML, Including Params</div>
This div will include all the parameters that a POST would, but they will be URL encoded and included in the URL, as per usual with a GET.
Notes
hx-params is inherited and can be placed on a parent element
hx-patch
The hx-patch attribute will cause an element to issue a PATCH to the specified URL and swap the HTML into the DOM using a swap strategy:
<button hx-patch="/account" hx-target="body">
Patch Your Account
</button>
This example will cause the button to issue a PATCH to /account and swap the returned HTML into the innerHTML of the body.
Notes
hx-patch is not inherited You can control the target of the swap using the hx-target attribute You can control the swap strategy by using the hx-swap attribute You can control what event triggers the request with the hx-trigger attribute You can control the data submitted with the request in various ways, documented here: Parameters
hx-post
The hx-post attribute will cause an element to issue a POST to the specified URL and swap the HTML into the DOM using a swap strategy:
<button hx-post="/account/enable" hx-target="body">
Enable Your Account
</button>
This example will cause the button to issue a POST to /account/enable and swap the returned HTML into the innerHTML of the body.
Notes
hx-post is not inherited You can control the target of the swap using the hx-target attribute You can control the swap strategy by using the hx-swap attribute You can control what event triggers the request with the hx-trigger attribute You can control the data submitted with the request in various ways, documented here: Parameters
hx-preserve
The hx-preserve attribute allows you to keep an element unchanged during HTML replacement.
Elements with hx-preserve set are preserved by id when htmx updates any ancestor element.
You must set an unchanging id on elements for hx-preserve to work.
The response requires an element with the same id, but its type and other attributes are ignored.
Note that some elements cannot unfortunately be preserved properly, such as <input type="text"> (focus and caret position are lost), iframes or certain types of videos.
To tackle some of these cases we recommend the morphdom extension, which does a more elaborate DOM reconciliation.
Notes
hx-preserve is not inherited
hx-prompt
The hx-prompt attribute allows you to show a prompt before issuing a request.
The value of the prompt will be included in the request in the HX-Prompt header.
Here is an example:
<button hx-delete="/account" hx-prompt="Enter your account name to confirm deletion">
Delete My Account
</button>
Notes
hx-prompt is inherited and can be placed on a parent element
hx-push-url
The hx-push-url attribute allows you to push a URL into the browser location history.
This creates a new history entry, allowing navigation with the browser’s back and forward buttons.
htmx snapshots the current DOM and saves it into its history cache, and restores from this cache on navigation.
The possible values of this attribute are:
true, which pushes the fetched URL into history.
false, which disables pushing the fetched URL if it would otherwise be pushed due to inheritance or hx-boost.
A URL to be pushed into the location bar.
This may be relative or absolute, as per history.pushState().
Here is an example:
<div hx-get="/account" hx-push-url="true">
Go to My Account
</div>
This will cause htmx to snapshot the current DOM to localStorage and push the URL `/account’ into the browser location bar.
Another example:
<div hx-get="/account" hx-push-url="/account/home">
Go to My Account
</div>
This will push the URL `/account/home’ into the location history.
Notes
hx-push-url is inherited and can be placed on a parent element The HX-Push-Url response header has similar behavior and can override this attribute.
The hx-history-elt attribute allows changing which element is saved in the history cache.
hx-put
The hx-put attribute will cause an element to issue a PUT to the specified URL and swap the HTML into the DOM using a swap strategy:
<button hx-put="/account" hx-target="body">
Put Money In Your Account
</button>
This example will cause the button to issue a PUT to /account and swap the returned HTML into the innerHTML of the body.
Notes
hx-put is not inherited You can control the target of the swap using the hx-target attribute You can control the swap strategy by using the hx-swap attribute You can control what event triggers the request with the hx-trigger attribute You can control the data submitted with the request in various ways, documented here: Parameters
hx-replace-url
The hx-replace-url attribute allows you to replace the current url of the browser location history.
The possible values of this attribute are:
true, which replaces the fetched URL in the browser navigation bar.
false, which disables replacing the fetched URL if it would otherwise be replaced due to inheritance.
A URL to be replaced into the location bar.
This may be relative or absolute, as per history.replaceState().
Here is an example:
<div hx-get="/account" hx-replace-url="true">
Go to My Account
</div>
This will cause htmx to snapshot the current DOM to localStorage and replace the URL `/account’ in the browser location bar.
Another example:
<div hx-get="/account" hx-replace-url="/account/home">
Go to My Account
</div>
This will replace the URL `/account/home’ in the browser location bar.
Notes
hx-replace-url is inherited and can be placed on a parent element The HX-Replace-Url response header has similar behavior and can override this attribute.
The hx-history-elt attribute allows changing which element is saved in the history cache.
The hx-push-url attribute is a similar and more commonly used attribute, which creates a new history entry rather than replacing the current one.
hx-request
The hx-request attribute allows you to configure various aspects of the request via the following attributes:
timeout - the timeout for the request, in milliseconds
credentials - if the request will send credentials
noHeaders - strips all headers from the request
These attributes are set using a JSON-like syntax:
<div ...
hx-request='{"timeout":100}'>
...
</div>
You may make the values dynamically evaluated by adding the javascript: or js: prefix:
<div ...
hx-request='js: timeout:getTimeoutSetting() '>
...
</div>
Notes
hx-request is merge-inherited and can be placed on a parent element
hx-select-oob
The hx-select-oob attribute allows you to select content from a response to be swapped in via an out-of-band swap.
The value of this attribute is comma separated list of elements to be swapped out of band.
This attribute is almost always paired with hx-select.
Here is an example that selects a subset of the response content:
<div>
<div id="alert"></div>
<button hx-get="/info"
hx-select="#info-details"
hx-swap="outerHTML"
hx-select-oob="#alert">
Get Info!
</button>
</div>
This button will issue a GET to /info and then select the element with the id info-details,
which will replace the entire button in the DOM, and, in addition, pick out an element with the id alert in the response and swap it in for div in the DOM with the same ID.
Each value in the comma separated list of values can specify any valid hx-swap strategy by separating the selector and the swap strategy with a :.
For example, to prepend the alert content instead of replacing it:
<div>
<div id="alert"></div>
<button hx-get="/info"
hx-select="#info-details"
hx-swap="outerHTML"
hx-select-oob="#alert:afterbegin">
Get Info!
</button>
</div>
Notes
hx-select-oob is inherited and can be placed on a parent element
hx-select
The hx-select attribute allows you to select the content you want swapped from a response.
The value of this attribute is a CSS query selector of the element or elements to select from the response.
Here is an example that selects a subset of the response content:
<div>
<button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML">
Get Info!
</button>
</div>
So this button will issue a GET to /info and then select the element with the id info-detail,
which will replace the entire button in the DOM.
Notes
hx-select is inherited and can be placed on a parent element
hx-swap-oob
The hx-swap-oob attribute allows you to specify that some content in a response should be swapped into the DOM somewhere other than the target, that is “Out of Band”.
This allows you to piggy back updates to other element updates on a response.
Consider the following response HTML:
<div>
...
</div>
<div id="alerts" hx-swap-oob="true">
Saved!
</div>
The first div will be swapped into the target the usual manner.
The second div, however, will be swapped in as a replacement for the element with the id alerts, and will not end up in the target.
The value of the hx-swap-oob can be:
true any valid hx-swap value any valid hx-swap value, followed by a colon, followed by a CSS selector
If the value is true or outerHTML (which are equivalent) the element will be swapped inline.
If a swap value is given, that swap strategy will be used.
If a selector is given, all elements matched by that selector will be swapped.
If not, the element with an ID matching the new content will be swapped.
Troublesome Tables
Note that you can use a template tag to encapsulate types of elements that, by the HTML spec, can’t stand on their own in the DOM (<tr>, <td>, <th>, <thead>, <tbody>, <tfoot>, <colgroup>, <caption> & <col>).
Here is an example with an out of band swap of a table row being encapsulated in this way:
<div>
...
</div>
<template>
<tr id="row" hx-swap-oob="true">
...
</tr>
</template>
Note that these template tags will be removed from the final content of the page.
Nested OOB Swaps
By default, any element with hx-swap-oob= attribute anywhere in the response is processed for oob swap behavior, including when an element is nested within the main response element.
This can be problematic when using template fragments where a fragment may be reused as a oob-swap target and also as part of a bigger fragment.
When the bigger fragment is the main response the inner fragment will still be processed as an oob swap, removing it from the dom.
This behavior can be changed by setting the config htmx.config.allowNestedOobSwaps to false.
If this config option is false, OOB swaps are only processed when the element is adjacent to the main response element, OOB swaps elsewhere will be ignored and oob-swap-related attributes stripped.
Notes
hx-swap-oob is not inherited
hx-swap
The hx-swap attribute allows you to specify how the response will be swapped in relative to the
target of an AJAX request.
If you do not specify the option, the default is
htmx.config.defaultSwapStyle (innerHTML).
The possible values of this attribute are:
innerHTML - Replace the inner html of the target element
outerHTML - Replace the entire target element with the response
textContent - Replace the text content of the target element, without parsing the response as HTML
beforebegin - Insert the response before the target element
afterbegin - Insert the response before the first child of the target element
beforeend - Insert the response after the last child of the target element
afterend - Insert the response after the target element
delete - Deletes the target element regardless of the response
none- Does not append content from response (out of band items will still be processed).
These options are based on standard DOM naming and the
Element.insertAdjacentHTML specification.
So in this code:
<div hx-get="/example" hx-swap="afterend">Get Some HTML & Append It</div>
The div will issue a request to /example and append the returned content after the div
Modifiers
The hx-swap attributes supports modifiers for changing the behavior of the swap.
They are outlined below.
Transition: transition
If you want to use the new View Transitions API when a swap occurs, you can use the transition:true option for your swap.
You can also enable this feature globally by setting the htmx.config.globalViewTransitions config setting to true.
Timing: swap & settle
You can modify the amount of time that htmx will wait after receiving a response to swap the content by including a swap modifier:
<!-- this will wait 1s before doing the swap after it is received -->
<div hx-get="/example" hx-swap="innerHTML swap:1s">Get Some HTML & Append It</div>
Similarly, you can modify the time between the swap and the settle logic by including a settle modifier:
<!-- this will wait 1s before doing the swap after it is received -->
<div hx-get="/example" hx-swap="innerHTML settle:1s">Get Some HTML & Append It</div>
These attributes can be used to synchronize htmx with the timing of CSS transition effects.
Title: ignoreTitle
By default, htmx will update the title of the page if it finds a <title> tag in the response content.
You can turn off this behavior by setting the ignoreTitle option to true.
Scrolling: scroll & show
You can also change the scrolling behavior of the target element by using the scroll and show modifiers, both of which take the values top and bottom:
<!-- this fixed-height div will scroll to the bottom of the div after content is appended -->
<div style="height:200px; overflow: scroll"
hx-get="/example"
hx-swap="beforeend scroll:bottom">
Get Some HTML & Append It & Scroll To Bottom
</div> <!-- this will get some content and add it to #another-div, then ensure that the top of #another-div is visible in the
viewport -->
<div hx-get="/example"
hx-swap="innerHTML show:top"
hx-target="#another-div">
Get Some Content
</div>
If you wish to target a different element for scrolling or showing, you may place a CSS selector after the scroll: or show:, followed by :top or :bottom:
<!-- this will get some content and swap it into the current div, then ensure that the top of #another-div is visible in the
viewport -->
<div hx-get="/example"
hx-swap="innerHTML show:#another-div:top">
Get Some Content
</div>
You may also use window:top and window:bottom to scroll to the top and bottom of the current window.
<!-- this will get some content and swap it into the current div, then ensure that the viewport is scrolled to the
very top -->
<div hx-get="/example"
hx-swap="innerHTML show:window:top">
Get Some Content
</div>
For boosted links and forms the default behaviour is show:top.
You can disable it globally with
htmx.config.scrollIntoViewOnBoost or you can use hx-swap="show:none" on an element basis.
<form action="/example" hx-swap="show:none">
...
</form>
Focus scroll
htmx preserves focus between requests for inputs that have a defined id attribute.
By default htmx prevents auto-scrolling to focused inputs between requests which can be unwanted behavior on longer requests when the user has already scrolled away.
To enable focus scroll you can use focus-scroll:true.
<input id="name" hx-get="/validation"
hx-swap="outerHTML focus-scroll:true"/>
Alternatively, if you want the page to automatically scroll to the focused element after each request you can change the htmx global configuration value htmx.config.defaultFocusScroll to true.
Then disable it for specific requests using focus-scroll:false.
<input id="name" hx-get="/validation"
hx-swap="outerHTML focus-scroll:false"/>
Notes
hx-swap is inherited and can be placed on a parent element The default value of this attribute is innerHTML
Due to DOM limitations, it’s not possible to use the outerHTML method on the <body> element.
htmx will change outerHTML on <body> to use innerHTML.
The default swap delay is 0ms The default settle delay is 20ms
hx-sync
The hx-sync attribute allows you to synchronize AJAX requests between multiple elements.
The hx-sync attribute consists of a CSS selector to indicate the element to synchronize on, followed optionally by a colon and then by an optional syncing strategy.
The available strategies are:
drop - drop (ignore) this request if an existing request is in flight (the default)
abort - drop (ignore) this request if an existing request is in flight, and, if that is not the case,
abort this request if another request occurs while it is still in flight
replace - abort the current request, if any, and replace it with this request
queue - place this request in the request queue associated with the given element
The queue modifier can take an additional argument indicating exactly how to queue:
queue first - queue the first request to show up while a request is in flight
queue last - queue the last request to show up while a request is in flight
queue all - queue all requests that show up while a request is in flight
Notes
hx-sync is inherited and can be placed on a parent element
This example resolves a race condition between a form’s submit request and an individual input’s validation request.
Normally, without using hx-sync, filling out the input and immediately submitting the form triggers two parallel requests to /validate and /store.
Using hx-sync="closest form:abort" on the input will watch for requests on the form and abort the input’s request if a form request is present or starts while the input request is in flight.
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
hx-sync="closest form:abort">
<button type="submit">Submit</button>
</form>
If you’d rather prioritize the validation request over the submit request, you can use the drop strategy.
This example will prioritize the validation request over the submit request so that if a validation request is in flight, the form cannot be submitted.
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
hx-sync="closest form:drop"
>
<button type="submit">Submit</button>
</form>
When dealing with forms that contain many inputs, you can prioritize the submit request over all input validation requests using the hx-sync replace strategy on the form tag.
This will cancel any in-flight validation requests and issue only the hx-post="/store" request.
If you’d rather abort the submit request and prioritize any existing validation requests you can use the hx-sync="this:abort" strategy on the form tag.
<form hx-post="/store" hx-sync="this:replace">
<input id="title" name="title" type="text" hx-post="/validate" hx-trigger="change" />
<button type="submit">Submit</button>
</form>
When implementing active search functionality the hx-trigger attribute’s delay modifier can be used to debounce the user’s input and avoid making multiple requests while the user types.
However, once a request is made, if the user begins typing again a new request will begin even if the previous one has not finished processing.
This example will cancel any in-flight requests and use only the last request.
In cases where the search input is contained within the target, then using hx-sync like this also helps reduce the chances that the input will be replaced while the user is still typing.
<input type="search"
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-sync="this:replace">
hx-target
The hx-target attribute allows you to target a different element for swapping than the one issuing the AJAX request.
The value of this attribute can be:
A CSS query selector of the element to target.
this which indicates that the element that the hx-target attribute is on is the target.
closest <CSS selector> which will find the closest ancestor element or itself, that matches the given CSS selector
(e.g.
closest tr will target the closest table row to the element).
find <CSS selector> which will find the first child descendant element that matches the given CSS selector.
next which resolves to element.nextElementSiblingnext <CSS selector> which will scan the DOM forward for the first element that matches the given CSS selector.
(e.g.
next .error will target the closest following sibling element with error class)
previous which resolves to element.previousElementSiblingprevious <CSS selector> which will scan the DOM backwards for the first element that matches the given CSS selector.
(e.g previous .error will target the closest previous sibling with error class)
Here is an example that targets a div:
<div>
<div id="response-div"></div>
<button hx-post="/register" hx-target="#response-div" hx-swap="beforeend">
Register!
</button>
</div>
The response from the /register url will be appended to the div with the id response-div.
This example uses hx-target="this" to make a link that updates itself when clicked:
<a hx-post="/new-link" hx-target="this" hx-swap="outerHTML">New link</a>
Notes
hx-target is inherited and can be placed on a parent element
hx-trigger
The hx-trigger attribute allows you to specify what triggers an AJAX request.
A trigger value can be one of the following:
An event name (e.g.
“click” or “my-custom-event”) followed by an event filter and a set of event modifiers A polling definition of the form every <timing declaration>
A comma-separated list of such events
Standard Events
A standard event, such as click can be specified as the trigger like so:
<div hx-get="/clicked" hx-trigger="click">Click Me</div>
Standard Event Filters
Events can be filtered by enclosing a boolean javascript expression in square brackets after the event name.
If this expression evaluates to true the event will be triggered, otherwise it will be ignored.
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
This event will trigger if a click event is triggered with the event.ctrlKey property set to true.
Conditions can also refer to global functions or state
<div hx-get="/clicked" hx-trigger="click[checkGlobalState()]">Control Click Me</div>
And can also be combined using the standard javascript syntax
<div hx-get="/clicked" hx-trigger="click[ctrlKey&&shiftKey]">Control-Shift Click Me</div>
Note that all symbols used in the expression will be resolved first against the triggering event, and then next against the global namespace, so myEvent[foo] will first look for a property named foo on the event, then look for a global symbol with the name foo
Standard Event Modifiers
Standard events can also have modifiers that change how they behave.
The modifiers are:
once - the event will only trigger once (e.g.
the first click)
changed - the event will only change if the value of the element has changed.
Please pay attention change is the name of the event and changed is the name of the modifier.
delay:<timing declaration> - a delay will occur before an event triggers a request.
If the event is seen again it will reset the delay.
throttle:<timing declaration> - a throttle will occur after an event triggers a request.
If the event is seen again before the delay completes, it is ignored, the element will trigger at the end of the delay.
from:<Extended CSS selector> - allows the event that triggers a request to come from another element in the document (e.g.
listening to a key event on the body, to support hot keys)
A standard CSS selector resolves to all elements matching that selector.
Thus, from:input would listen on every input on the page.
The CSS selector is only evaluated once and is not re-evaluated when the page changes.
If you need to detect dynamically added elements use an event filter, for example click[event.target.matches('input')]
The extended CSS selector here allows for the following non-standard CSS values:
document - listen for events on the document
window - listen for events on the window
closest <CSS selector> - finds the closest ancestor element or itself, matching the given css selector
find <CSS selector> - finds the closest child matching the given css selector
next resolves to element.nextElementSiblingnext <CSS selector> scans the DOM forward for the first element that matches the given CSS selector.
(e.g.
next .error will target the closest following sibling element with error class)
previous resolves to element.previousElementSiblingprevious <CSS selector> scans the DOM backwards for the first element that matches the given CSS selector.
(e.g previous .error will target the closest previous sibling with error class)
target:<CSS selector> - allows you to filter via a CSS selector on the target of the event.
This can be useful when you want to listen for triggers from elements that might not be in the DOM at the point of initialization, by, for example, listening on the body,
but with a target filter for a child element
consume - if this option is included the event will not trigger any other htmx requests on parents (or on elements listening on parents)
queue:<queue option> - determines how events are queued if an event occurs while a request for another event is in flight.
Options are:
first - queue the first event
last - queue the last event (default)
all - queue all events (issue a request for each event)
none - do not queue new events
Here is an example of a search box that searches on keyup, but only if the search value has changed and the user hasn’t typed anything new for 1 second:
<input name="q"
hx-get="/search" hx-trigger="keyup changed delay:1s"
hx-target="#search-results"/>
The response from the /search url will be appended to the div with the id search-results.
Non-standard Events
There are some additional non-standard events that htmx supports:
load - triggered on load (useful for lazy-loading something)
revealed - triggered when an element is scrolled into the viewport (also useful for lazy-loading).
If you are using overflow in css like overflow-y: scroll you should use intersect once instead of revealed.
intersect - fires once when an element first intersects the viewport.
This supports two additional options:
root:<selector> - a CSS selector of the root element for intersection
threshold:<float> - a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire the event on
Triggering via the HX-Trigger header
If you’re trying to fire an event from HX-Trigger response header, you will likely want to use the from:body modifier.
E.g.
if you send a header like this HX-Trigger: my-custom-event with a response, an element would likely need to look like this:
<div hx-get="/example" hx-trigger="my-custom-event from:body">
Triggered by HX-Trigger header...
</div>
in order to fire.
This is because the header will likely trigger the event in a different DOM hierarchy than the element that you wish to be triggered.
For a similar reason, you will often listen for hot keys from the body.
Polling
By using the syntax every <timing declaration> you can have an element poll periodically:
<div hx-get="/latest_updates" hx-trigger="every 1s">
Nothing Yet!
</div>
This example will issue a GET to the /latest_updates URL every second and swap the results into the innerHTML of this div.
If you want to add a filter to polling, it should be added after the poll declaration:
<div hx-get="/latest_updates" hx-trigger="every 1s [someConditional]">
Nothing Yet!
</div>
Multiple Triggers
Multiple triggers can be provided, separated by commas.
Each trigger gets its own options.
<div hx-get="/news" hx-trigger="load, click delay:1s"></div>
This example will load /news immediately on page load, and then again with a delay of one second after each click.
Via JavaScript
The AJAX request can be triggered via JavaScript htmx.trigger(), too.
Notes
hx-trigger is not inherited
hx-trigger can be used without an AJAX request, in which case it will only fire the htmx:trigger event In order to pass a CSS selector that contains whitespace (e.g.
form input) to the from- or target-modifier, surround the selector in parentheses or curly brackets (e.g.
from:(form input) or from:nearest (form input))
hx-validate
The hx-validate attribute will cause an element to validate itself by way of the HTML5 Validation API before it submits a request.
Only <form> elements validate data by default, but other elements do not.
Adding hx-validate="true" to <input>, <textarea> or <select> enables validation before sending requests.
Notes
hx-validate is not inherited
hx-vals
The hx-vals attribute allows you to add to the parameters that will be submitted with an AJAX request.
By default, the value of this attribute is a list of name-expression values in JSON (JavaScript Object Notation) format.
If you wish for hx-vals to evaluate the values given, you can prefix the values with javascript: or js:.
<div hx-get="/example" hx-vals='{"myVal": "My Value"}'>Get Some HTML, Including A Value in the Request</div>
<div hx-get="/example" hx-vals='js:{myVal: calculateValue()}'>Get Some HTML, Including a Dynamic Value from Javascript in the Request</div>
When using evaluated code you can access the event object.
This example includes the value of the last typed key within the input.
<div hx-get="/example" hx-trigger="keyup" hx-vals='js:{lastKey: event.key}'>
<input type="text" />
</div>
Security Considerations
By default, the value of hx-vals must be valid JSON.
It is not dynamically computed.
If you use the javascript: prefix, be aware that you are introducing security considerations, especially when dealing with user input such as query strings or user-generated content, which could introduce a Cross-Site Scripting (XSS) vulnerability.
Notes
hx-vals is inherited and can be placed on a parent element.
A child declaration of a variable overrides a parent declaration.
Input values with the same name will be overridden by variable declarations.
hx-vars
NOTE: hx-vars has been deprecated in favor of hx-vals, which is safer by default.
The hx-vars attribute allows you to dynamically add to the parameters that will be submitted with an AJAX request.
The value of this attribute is a comma separated list of name:<expression> values, the same as the internal syntax of javascript Object Literals.
<div hx-get="/example" hx-vars="myVar:computeMyVar()">Get Some HTML, Including A Dynamic Value in the Request</div>
Security Considerations
The expressions in hx-vars are dynamically computed which allows you to add JavaScript code that will be executed.
Be careful to never trust user input in your expressions as this may lead to a Cross-Site Scripting (XSS) vulnerability.
If you are dealing with user input such as query strings or user-generated content, consider using hx-vals which is a safer alternative.
Notes
hx-vars is inherited and can be placed on a parent element.
A child declaration of a variable overrides a parent declaration.
Input values with the same name will be overridden by variable declarations.
HX-Location Response Header
This response header can be used to trigger a client side redirection without reloading the whole page.
Instead of changing the page’s location it will act like following a hx-boost link, creating a new history entry, issuing an ajax request to the value of the header and pushing the path into history.
A sample response would be:
HX-Location: /test
Which would push the client to test as if the user had clicked on <a href="/test" hx-boost="true"> If you want to redirect to a specific target on the page rather than the default of document.body, you can pass more details along with the event, by using JSON for the value of the header:
HX-Location: {"path":"/test2", "target":"#testdiv"}
Path is required and is url to load the response from.
The rest of the data mirrors the ajax api context, which is:
source - the source element of the request
event - an event that “triggered” the request
handler - a callback that will handle the response HTML
target - the target to swap the response into
swap - how the response will be swapped in relative to the target
values - values to submit with the request
headers - headers to submit with the request
select - allows you to select the content you want swapped from a response
HX-Push-Url Response Header
The HX-Push-Url header allows you to push a URL into the browser location history.
This creates a new history entry, allowing navigation with the browser’s back and forward buttons.
This is similar to the hx-push-url attribute.
If present, this header overrides any behavior defined with attributes.
The possible values for this header are:
A URL to be pushed into the location bar.
This may be relative or absolute, as per history.pushState().
false, which prevents the browser’s history from being updated.
HX-Replace-Url Response Header
The HX-Replace-Url header allows you to replace the current URL in the browser location history.
This does not create a new history entry; in effect, it removes the previous current URL from the browser’s history.
This is similar to the hx-replace-url attribute.
If present, this header overrides any behavior defined with attributes.
The possible values for this header are:
A URL to replace the current URL in the location bar.
This may be relative or absolute, as per history.replaceState(), but must have the same origin as the current URL.
false, which prevents the browser’s current URL from being updated.
HX-Trigger Response Headers
These response headers can be used to trigger client side actions on the target element within a response to htmx.
You can trigger a single event or as many uniquely named events as you would like.
The headers are:
HX-Trigger - trigger events as soon as the response is received.
HX-Trigger-After-Settle - trigger events after the settling step.
HX-Trigger-After-Swap - trigger events after the swap step.
To trigger a single event with no additional details you can simply send the event name in a header like so:
HX-Trigger: myEvent This will trigger myEvent on the triggering element and will bubble up to the body.
As an example you could listen for this event like this:
document.body.addEventListener("myEvent", function(evt){
alert("myEvent was triggered!");
})
… or like this, if you’re trying to trigger some element without using JS code:
<!-- Since it bubbles up to the <body>, we must use the `from:body` modifier below -->
<div hx-trigger="myEvent from:body" hx-get="/example"></div>
If you want to pass details along with the event, you can move to JSON for the value of the trigger:
HX-Trigger: {"showMessage":"Here Is A Message"} To handle this event you would write the following code:
document.body.addEventListener("showMessage", function(evt){
alert(evt.detail.value);
})
Note that the value of the message was put into the detail.value slot.
If you wish to pass multiple pieces of data you can use a nested JSON object on the right hand side of the JSON object:
HX-Trigger: {"showMessage":{"level" : "info", "message" : "Here Is A Message"}} And handle this event like so:
document.body.addEventListener("showMessage", function(evt){
if(evt.detail.level === "info"){
alert(evt.detail.message);
}
})
Each property of the JSON object on the right hand side will be copied onto the details object for the event.
Multiple Triggers
If you wish to invoke multiple events, you can simply add additional properties to the top level JSON object:
HX-Trigger: {"event1":"A message", "event2":"Another message"} You may also trigger multiple events with no additional details by sending event names separated by commas, like so:
HX-Trigger: event1, event2 Using events gives you a lot of flexibility to add functionality to normal htmx responses.
While it is not a focus of the library, htmx does provide a small API of helper methods, intended mainly for extension development or for working with events.
The hyperscript project is intended to provide more extensive scripting support for htmx-based applications.
Method - htmx.addClass()
This method adds a class to the given element.
Parameters
elt - the element to add the class to
class - the class to add
or
elt - the element to add the class to
class - the class to add
delay - delay (in milliseconds ) before class is added
Example
// add the class 'myClass' to the element with the id 'demo'
htmx.addClass(htmx.find('#demo'), 'myClass');
// add the class 'myClass' to the element with the id 'demo' after 1 second
htmx.addClass(htmx.find('#demo'), 'myClass', 1000);
Method - htmx.ajax()
Issues an htmx-style AJAX request. This method returns a Promise, so a callback can be executed after the content has been inserted into the DOM.
Parameters
verb - ‘GET’, ‘POST’, etc.
path - the URL path to make the AJAX
element - the element to target (defaults to the body)
or
verb - ‘GET’, ‘POST’, etc.
path - the URL path to make the AJAX
selector - a selector for the target
or
verb - ‘GET’, ‘POST’, etc.
path - the URL path to make the AJAX
context - a context object that contains any of the following
source - the source element of the request, hx-* attrs which affect the request will be resolved against that element and its ancestors
event - an event that “triggered” the request
handler - a callback that will handle the response HTML
target - the target to swap the response into
swap - how the response will be swapped in relative to the target
values - values to submit with the request
headers - headers to submit with the request
select - allows you to select the content you want swapped from a response
Example
// issue a GET to /example and put the response HTML into #myDiv
htmx.ajax('GET', '/example', '#myDiv')
// issue a GET to /example and replace #myDiv with the response
htmx.ajax('GET', '/example', {target:'#myDiv', swap:'outerHTML'})
// execute some code after the content has been inserted into the DOM
htmx.ajax('GET', '/example', '#myDiv').then(() => {
// this code will be executed after the 'htmx:afterOnLoad' event,
// and before the 'htmx:xhr:loadend' event
console.log('Content inserted successfully!');
});
Method - htmx.closest()
Finds the closest matching element in the given elements parentage, inclusive of the element
Parameters
elt - the element to find the selector from
selector - the selector to find
Example
// find the closest enclosing div of the element with the id 'demo'
htmx.closest(htmx.find('#demo'), 'div');
Property - htmx.config
A property holding the configuration htmx uses at runtime.
Note that using a meta tag is the preferred mechanism for setting these properties.
Properties
attributesToSettle:["class", "style", "width", "height"] - array of strings: the attributes to settle during the settling phase
refreshOnHistoryMiss:false - boolean: if set to true htmx will issue a full page refresh on history misses rather than use an AJAX request
defaultSettleDelay:20 - int: the default delay between completing the content swap and settling attributes
defaultSwapDelay:0 - int: the default delay between receiving a response from the server and doing the swap
defaultSwapStyle:'innerHTML' - string: the default swap style to use if hx-swap is omitted
historyCacheSize:10 - int: the number of pages to keep in localStorage for history support
historyEnabled:true - boolean: whether or not to use history
includeIndicatorStyles:true - boolean: if true, htmx will inject a small amount of CSS into the page to make indicators invisible unless the htmx-indicator class is present
indicatorClass:'htmx-indicator' - string: the class to place on indicators when a request is in flight
requestClass:'htmx-request' - string: the class to place on triggering elements when a request is in flight
addedClass:'htmx-added' - string: the class to temporarily place on elements that htmx has added to the DOM
settlingClass:'htmx-settling' - string: the class to place on target elements when htmx is in the settling phase
swappingClass:'htmx-swapping' - string: the class to place on target elements when htmx is in the swapping phase
allowEval:true - boolean: allows the use of eval-like functionality in htmx, to enable hx-vars, trigger conditions & script tag evaluation. Can be set to false for CSP compatibility.
allowScriptTags:true - boolean: allows script tags to be evaluated in new content
inlineScriptNonce:'' - string: the nonce to add to inline scripts
inlineStyleNonce:'' - string: the nonce to add to inline styles
withCredentials:false - boolean: allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates
timeout:0 - int: the number of milliseconds a request can take before automatically being terminated
wsReconnectDelay:'full-jitter' - string/function: the default implementation of getWebSocketReconnectDelay for reconnecting after unexpected connection loss by the event code Abnormal Closure, Service Restart or Try Again LaterwsBinaryType:'blob' - string: the the type of binary data being received over the WebSocket connection
disableSelector:"[hx-disable], [data-hx-disable]" - array of strings: htmx will not process elements with this attribute on it or a parent
scrollBehavior:'smooth' - string: the behavior for a boosted link on page transitions. The allowed values are auto and smooth. Smooth will smoothscroll to the top of the page while auto will behave like a vanilla link.
defaultFocusScroll:false - boolean: if the focused element should be scrolled into view, can be overridden using the focus-scroll swap modifier
getCacheBusterParam:false - boolean: if set to true htmx will append the target element to the GET request in the format org.htmx.cache-buster=targetElementIdglobalViewTransitions:false - boolean: if set to true, htmx will use the View Transition API when swapping in new content.
methodsThatUseUrlParams:["get"] - array of strings: htmx will format requests with these methods by encoding their parameters in the URL, not the request body
selfRequestsOnly:true - boolean: whether to only allow AJAX requests to the same domain as the current document
ignoreTitle:false - boolean: if set to true htmx will not update the title of the document when a title tag is found in new content
scrollIntoViewOnBoost:true - boolean: whether or not the target of a boosted element is scrolled into the viewport. If hx-target is omitted on a boosted element, the target defaults to body, causing the page to scroll to the top.
triggerSpecsCache:null - object: the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a proxy object |
Example
// update the history cache size to 30
htmx.config.historyCacheSize = 30;
Property - htmx.createEventSource
A property used to create new Server Sent Event sources. This can be updated to provide custom SSE setup.
Value
func(url) - a function that takes a URL string and returns a new EventSource
Example
// override SSE event sources to not use credentials
htmx.createEventSource = function(url) {
return new EventSource(url, {withCredentials:false});
};
Property - htmx.createWebSocket
A property used to create new WebSocket. This can be updated to provide custom WebSocket setup.
Value
func(url) - a function that takes a URL string and returns a new WebSocket
Example
// override WebSocket to use a specific protocol
htmx.createWebSocket = function(url) {
return new WebSocket(url, ['wss']);
};
name - the extension name
ext - the extension definition
Example
// defines a silly extension that just logs the name of all events triggered
htmx.defineExtension("silly", {
onEvent : function(name, evt) {
console.log("Event " + name + " was triggered!")
}
});
Method - htmx.find()
Finds an element matching the selector
Parameters
selector - the selector to match
or
elt - the root element to find the matching element in, inclusive
selector - the selector to match
Example
// find div with id my-div
var div = htmx.find("#my-div")
// find div with id another-div within that div
var anotherDiv = htmx.find(div, "#another-div")
Method - htmx.findAll()
Finds all elements matching the selector
Parameters
selector - the selector to match
or
elt - the root element to find the matching elements in, inclusive
selector - the selector to match
Example
// find all divs
var allDivs = htmx.findAll("div")
// find all paragraphs within a given div
var allParagraphsInMyDiv = htmx.findAll(htmx.find("#my-div"), "p")
Method - htmx.logAll()
Log all htmx events, useful for debugging.
Example
htmx.logAll();
Method - htmx.logNone()
Log no htmx events, call this to turn off the debugger if you previously enabled it.
Example
htmx.logNone();
Property - htmx.logger
The logger htmx uses to log with
Value
func(elt, eventName, detail) - a function that takes an element, eventName and event detail and logs it
eventName - the event name to remove the listener from
listener - the listener to remove
or
target - the element to remove the listener from
eventName - the event name to remove the listener from
listener - the listener to remove
Example
// remove this click listener from the body
htmx.off("click", myEventListener);
// remove this click listener from the given div
htmx.off("#my-div", "click", myEventListener)
Method - htmx.on()
Adds an event listener to an element
Parameters
eventName - the event name to add the listener for
listener - the listener to add
or
target - the element to add the listener to
eventName - the event name to add the listener for
listener - the listener to add
Example
// add a click listener to the body
var myEventListener = htmx.on("click", function(evt){ console.log(evt); });
// add a click listener to the given div
var myEventListener = htmx.on("#my-div", "click", function(evt){ console.log(evt); });
Method - htmx.onLoad()
Adds a callback for the htmx:load event. This can be used to process new content, for example initializing the content with a javascript library
Parameters
callback(elt) - the callback to call on newly loaded content
Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes.
Caution: Accepts an int followed by either s or ms. All other values use parseFloat
Parameters
str - timing string
Example
// returns 3000
var milliseconds = htmx.parseInterval("3s");
// returns 3 - Caution
var milliseconds = htmx.parseInterval("3m");
Method - htmx.process()
Processes new content, enabling htmx behavior. This can be useful if you have content that is added to the DOM outside of the normal htmx request cycle but still want htmx attributes to work.
Parameters
elt - element to process
Example
document.body.innerHTML = "<div hx-get='/example'>Get it!</div>"
// process the newly added content
htmx.process(document.body);
Method - htmx.remove()
Removes an element from the DOM
Parameters
elt - element to remove
or
elt - element to remove
delay - delay (in milliseconds ) before element is removed
Example
// removes my-div from the DOM
htmx.remove(htmx.find("#my-div"));
// removes my-div from the DOM after a delay of 2 seconds
htmx.remove(htmx.find("#my-div"), 2000);
Method - htmx.removeClass()
Removes a class from the given element
Parameters
elt - element to remove the class from
class - the class to remove
or
elt - element to remove the class from
class - the class to remove
delay - delay (in milliseconds ) before class is removed
Example
// removes .myClass from my-div
htmx.removeClass(htmx.find("#my-div"), "myClass");
// removes .myClass from my-div after 6 seconds
htmx.removeClass(htmx.find("#my-div"), "myClass", 6000);
Method - htmx.removeExtension()
Removes the given extension from htmx
Parameters
name - the name of the extension to remove
Example
htmx.removeExtension("my-extension");
Method - htmx.swap()
Performs swapping (and settling) of HTML content
Parameters
target - the HTML element or string selector of swap target
content - string representation of content to be swapped
swapSpec - swapping specification, representing parameters from hx-swapswapStyle (required) - swapping style (innerHTML, outerHTML, beforebegin etc)
swapDelay, settleDelay (number) - delays before swapping and settling respectively
transition (bool) - whether to use HTML transitions for swap
ignoreTitle (bool) - disables page title updates
head (string) - specifies head tag handling strategy (merge or append). Leave empty to disable head handling
scroll, scrollTarget, show, showTarget, focusScroll - specifies scroll handling after swap
swapOptions - additional optional parameters for swapping
select - selector for the content to be swapped (equivalent of hx-select)
selectOOB - selector for the content to be swapped out-of-band (equivalent of hx-select-oob)
eventInfo - an object to be attached to htmx:afterSwap and htmx:afterSettle elements
anchor - an anchor element that triggered scroll, will be scrolled into view on settle. Provides simple alternative to full scroll handling
contextElement - DOM element that serves as context to swapping operation. Currently used to find extensions enabled for specific element
afterSwapCallback, afterSettleCallback - callback functions called after swap and settle respectively. Take no arguments
Example
// swap #output element inner HTML with div element with "Swapped!" text
htmx.swap("#output", "<div>Swapped!</div>", {swapStyle: 'innerHTML'});
Method - htmx.takeClass()
Takes the given class from its siblings, so that among its siblings, only the given element will have the class.
Parameters
elt - the element that will take the class
class - the class to take
Example
// takes the selected class from tab2's siblings
htmx.takeClass(htmx.find("#tab2"), "selected");
Method - htmx.toggleClass()
Toggles the given class on an element
Parameters
elt - the element to toggle the class on
class - the class to toggle
Example
// toggles the selected class on tab2
htmx.toggleClass(htmx.find("#tab2"), "selected");
Method - htmx.trigger()
Triggers a given event on an element
Parameters
elt - the element to trigger the event on
name - the name of the event to trigger
detail - details for the event
Example
// triggers the myEvent event on #tab2 with the answer 42
htmx.trigger("#tab2", "myEvent", {answer:42});
Method - htmx.values()
Returns the input values that would resolve for a given element via the htmx value resolution mechanism
Parameters
elt - the element to resolve values on
request type - the request type (e.g. get or post) non-GET’s will include the enclosing form of the element.
Defaults to post
Example
// gets the values associated with this form
var values = htmx.values(htmx.find("#myForm"));